import { GlobalErrorTypes } from '../error';
import defineRoles from '../roles/role';
import { Controller } from 'egg';
import { subject } from '@casl/ability';
import { permittedFieldsOf } from '@casl/ability/extra';
import { difference, assign } from 'lodash/fp';

const caslMethodMapping:Record<string, string> = {
  GET: 'read',
  POST: 'create',
  PATCH: 'update',
  DELETE: 'delete',
};
interface ModelMapping {
  mongoose: string;
  casl: string;
}
interface IOptions {
  // 自定义action
  action?: string;
  // 查找记录时候的key, 默认为id
  key?: string;
  // 查找记录时候value的来源默认为ctx.params
  // 来源于对应URL参数或者ctx.request.body, valueKey 数据来源的键值
  value?: { type: 'params' | 'body', valueKey: string }
}
const fieldsOptions = { fieldsFrom: rule => rule.fields || [] };
const defaultSearchOptions = {
  key: 'id',
  value: { type: 'params', valueKey: 'id' },
};
/**
 *
 * @param modelName model的名称，可以是普通的字符串，也可以是casl和mongoose的映射关系
 * @param errorType 返回错误的类型，来自GlobalErrorTypes
 * @param options 特殊配置选项，可以定义action以及查询条件，详见上面的IOptions选项
 */
export default function validateInput(modelName:string | ModelMapping, errorType: GlobalErrorTypes, options: IOptions = {}) {
  return function(_protoType, _key: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    descriptor.value = async function(...args: any[]) {
      const that = this as Controller;
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const { ctx } = that;
      const { method } = ctx.request;
      const searchOptions = assign(defaultSearchOptions, options || {});
      const { key, value } = searchOptions;
      const { type, valueKey } = value;
      // 构建query
      const source = type === 'params' ? ctx.params : ctx.request.body;
      const query = {
        [key]: source[valueKey],
      };
      // 构建modelname
      const mongooseModelName = typeof modelName === 'string' ? modelName : modelName.mongoose;
      const caslModelName = typeof modelName === 'string' ? modelName : modelName.casl;
      const action = (options && options.action) ? options.action : caslMethodMapping[method];
      if (!ctx.state || !ctx.state.user) {
        return ctx.helper.error({ ctx, errorType });
      }
      let permission = false;
      let keyPermission = true;
      const ability = defineRoles(ctx.state.user);
      // 获取到规则中所有的条件
      const rule = ability.relevantRuleFor(action, caslModelName);
      if (rule && rule.conditions) {
        const certianRecord = await ctx.model[mongooseModelName].findOne(query).lean();
        permission = ability.can(action, subject(caslModelName, certianRecord));
      } else {
        permission = ability.can(action, caslModelName);
      }
      if (rule && rule.fields) {
        // 获取到对相应模块的相应字段进行操作的权限
        const fields = permittedFieldsOf(ability, action, caslModelName, fieldsOptions);
        if (fields.length > 0) {
          const payloadKeys = Object.keys(ctx.request.body);
          const diffKeys = difference(payloadKeys, fields);
          keyPermission = diffKeys.length === 0;
        }
      }
      if (!permission && !keyPermission) {
        return ctx.helper.error({ ctx, errorType });
      }
      await originalMethod.apply(this, args);
    };
  };
}
